#################################################################
# Name: Player.py 						#
# Author: Francisco Rivera 					#
# Date Completed: 8/13/2013 					#
#################################################################

import math
import numpy
import warnings

warnings.simplefilter('ignore', numpy.RankWarning)


class BasePlayer(object):
	
	def __str__(self):
		try:
			return self.name
		except AttributeError:
			return super(BasePlayer, self).__repr__()

	def hunt_choices(*args, **kwargs):
		raise NotImplementedError("A strategy is needed")

	def hunt_outcomes(*args, **kwargs):
		pass

	def round_end(*args, **kwargs):
		pass

class Player(BasePlayer):
	
	def __init__(self):
		self.name = "Francisco"
		self.hunts = 0
		self.slacks = 0
		self.gamma = [0]
		self.overallAvg = 0
		self.round = 0
		self.roundRep = [0.0]
		self.roundOtherRep = [0.0]
		self.coeff = [0.0, 0.0, 0.0, 0.0]

	def setBeta(self, counter):
		self.coeff = numpy.polyfit(self.roundRep[int(math.floor((2**counter-1)*len(self.roundRep)/2**counter)):], self.roundOtherRep[int(math.floor((2**counter-1)*len(self.roundOtherRep)/2**counter)):], 3)

	def beta(self, reputation):

		if self.round > 11:
			counter = 1.0
			if self.round % 50 == 0:
				self.setBeta(counter)
				counter += 1
			while abs(self.coeff[0]/4.0 + self.coeff[1]/3.0 + self.coeff[2]/2.0 + self.coeff[3] - self.overallAvg) > .1 and counter < 10:
				if max(self.roundRep[int(math.floor((2**counter-1)*len(self.roundRep)/2**counter)):]) - min(self.roundRep[int(math.floor((2**counter-1)*len(self.roundRep)/2**counter)):]) > 0.2:
					self.setBeta(counter)
				counter += 1
			return self.coeff[0]*(reputation**3) + self.coeff[1]*(reputation**2) + self.coeff[2]*reputation + self.coeff[3]
		else:
			return self.overallAvg
		
	
	def score(self, reputation):
		return 3*self.beta(reputation) - reputation - 2


	def sortReps(self, player_reputations):

		if len(player_reputations) == 1:
			return player_reputations

		max = player_reputations[0]
		maxI = 0
		for i in range(len(player_reputations)):
			if self.score(player_reputations[i]) > self.score(max):
				max = player_reputations[i]
				maxI = i

		abbreviated = []
		for i in range(len(player_reputations)):
			if i != maxI:
				abbreviated.append(player_reputations[i])

		sorted = self.sortReps(abbreviated)
		sorted.append(max)
		return sorted
				

	def gammaFunc(self,
			round_number,
			current_food,
			current_reputation,
			m,
			player_reputations):
		
		counter = round_number
		if len(self.gamma) <= round_number:
			self.gamma.append(len(player_reputations))
		else:
			self.gamma[round_number] = len(player_reputations)

		avg = 0.0
		for reputation in player_reputations:
			avg += reputation
		avg = avg*1.0 / len(player_reputations)
	
		sum = 0
		val = 0
		counter += 1

		while counter < round_number + 1500:
			sum = 0
			val = 0
			if counter % 10 == 0:
				for i in range(counter):
					sum += self.gamma[i]

				for reputation in player_reputations:
					if 300*self.gamma[1] > sum * (self.score(reputation) + 2*avg):
						val += 1
			else:
				val = self.gamma[counter-1]

			if len(self.gamma) <= counter:
				self.gamma.append(val)
			else:
				self.gamma[counter] = val
			counter += 1

	def numHunts(self,
			round_number,
			current_food,
			current_reputation,
			m,
			player_reputations):


		if round_number == 1:
			return 0
		elif round_number == 2:
			return int(math.floor(len(player_reputations)*0.2))
		elif round_number == 3:
			return int(math.floor(len(player_reputations)*0.4))
		elif round_number == 4:
			return int(math.floor(len(player_reputations)*0.6))
		elif round_number == 5:
			return int(math.floor(len(player_reputations)*0.8))
		elif round_number >= 6 and round_number <= 10:
			return len(player_reputations)
		elif round_number == 11:
			self.setBeta(0)
			
	
		futureRounds = 0
		for i in range(round_number + 1, round_number+101):
			futureRounds += self.gamma[i]

		maxVal = 3*self.gamma[round_number]*self.beta(current_reputation) + 3*(self.beta(1.0*(self.hunts)/(self.hunts + self.slacks + self.gamma[round_number])))*futureRounds
		maxN = 0

		for N in range(1,self.gamma[round_number] +1):
			if (3*self.gamma[round_number]*self.beta(current_reputation) - N + 3*(self.beta(1.0*(self.hunts + N)/(self.hunts + self.slacks + self.gamma[round_number])))*futureRounds - (3.0*N/self.gamma[round_number])) > maxVal:
				maxVal = (3*self.gamma[round_number]*self.beta(current_reputation) - N + 3*(self.beta(1.0*(self.hunts + N)/(self.hunts + self.slacks + self.gamma[round_number])))*futureRounds - (3.0*N/self.gamma[round_number]))
				maxN = N

		return maxN
	

	def hunt_choices(
			self,
			round_number,
			current_food,
			current_reputation,
			m,
			player_reputations,
			):
	
		self.round = round_number

		if round_number > 1:
			self.gammaFunc(round_number, current_food, current_reputation, m, player_reputations)
		else:
			self.gamma.append(len(player_reputations))

		decisions = []
		sorted = self.sortReps(player_reputations)
		numHunts = self.numHunts(round_number, current_food, current_reputation, m, player_reputations)
		threshold = 0
		if numHunts == 0:
			threshold = -100
		else:
			threshold = self.score(sorted[numHunts - 1])

		done = 0

		for reputation in player_reputations:
			if self.score(reputation) < threshold:
				decisions.append('h')
				self.hunts += 1
				done += 1
			elif self.score(reputation) == threshold:
				if done < numHunts:
					decisions.append('h')
					self.hunts += 1
					done += 1
				else:
					decisions.append('s')
					self.slacks += 1
			else:
				decisions.append('s')
				self.slacks += 1

		if round_number > 1:
			self.roundRep.append((1.0*self.hunts - 1.0*numHunts) / (self.hunts + self.slacks - len(player_reputations)))
		else:
			self.roundRep.append(0.5)
		

		return decisions 

	def hunt_outcomes(self, food_earnings):
		otherHunts = 0.0
		otherSlacks = 0.0
		for earning in food_earnings:
			if earning == 0 or earning == 1:
				otherHunts += 1

		self.roundOtherRep.append(otherHunts * 1.0 / len(food_earnings))

	def round_end(self, award, m, number_hunters):
		self.overallAvg = number_hunters / (1.0 * self.gamma[self.round] * (self.gamma[self.round] + 1))
		
